Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Nov 12, 2025

Summary

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • Refactor
    • Streamlined internal request handling by unifying the connection strategy across all request types. The primary connection is now consistently used for both standard and streaming operations. This may influence retry behavior and error handling pathways for streaming requests, potentially improving overall request resilience and consistency.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

Walkthrough

The perform_request method in react_on_rails_pro/lib/react_on_rails_pro/request.rb was refactored to simplify connection selection logic, removing the streaming-specific non-retry path and consistently using the primary HTTPX connection regardless of streaming status.

Changes

Cohort / File(s) Summary
Connection selection simplification
react_on_rails_pro/lib/react_on_rails_pro/request.rb
Removed conditional logic for streaming-specific non-retrying connection; now always uses primary connection for all requests

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Specific areas requiring attention:
    • Impact on streaming request retry behavior and error handling
    • Body duplication implications for streaming requests
    • Comparison with error flow behavior in both streaming and non-streaming paths
    • Potential cascading effects from removing the non-retry fallback for streaming scenarios

Possibly related PRs

  • PR #1900: Directly inverse change—adds non-retry connection specifically for streaming to prevent body duplication, whereas this PR removes that streaming-specific path and consolidates to primary connection strategy.

Poem

🐰 Connection paths once forked at streaming's door,
Now unified, one thread to explore!
The non-retry streams dissolve with grace,
As primary paths embrace their place.
Simpler flows, though retry risks may shift—
One rope to climb, a logical gift! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Increase delayBetweenIndividualWorkerRestarts' does not match the actual changes, which involve modifying HTTPX connection selection logic in the request handling code. Update the PR title to accurately reflect the main change: something like 'Unify HTTPX connection selection for streaming requests' or 'Remove streaming-specific non-retry connection path'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch investigate-failing-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Nov 12, 2025

Code Review

Summary

This PR changes the delayBetweenIndividualWorkerRestarts configuration from a CI-conditional value (env.CI ? 0.01 : 1) to a constant value of 1 minute. This removes the fast worker restart behavior that was previously used in CI environments.

Analysis

What Changed:

  • Before: delayBetweenIndividualWorkerRestarts: env.CI ? 0.01 : 1
  • After: delayBetweenIndividualWorkerRestarts: 1

Impact:

  • In CI environments, worker restarts will now take 1 minute between each worker instead of 0.6 seconds (0.01 minutes)
  • Given that allWorkersRestartInterval is set to 2 minutes in CI and there are 2 workers (config.workersCount = 2), this means the full restart cycle will take 1 minute instead of ~0.6 seconds

Concerns & Questions

  1. Missing Context: The PR description is incomplete - it doesn't explain:

    • Why this change is needed
    • What problem it solves
    • Whether this is related to the "investigate-failing-tests" branch name
  2. Potential Issues:

    • Was the fast restart (0.01 min) causing race conditions or test flakiness in CI?
    • Is this addressing a specific test failure? If so, which test(s)?
    • Could this significantly slow down CI runs if worker restarts happen during tests?
  3. PR Checklist Not Completed:

    • ❌ No tests added/updated
    • ❌ No documentation updated
    • ❌ No CHANGELOG entry (per CLAUDE.md, this may not need one if it's just a test config change)
    • ❌ No description of the bug/issue being fixed
  4. Code Quality:

    • ✅ The change itself is clean and simple
    • ✅ Follows the existing code style
    • ⚠️ No obvious bugs introduced by the change itself

Recommendations

Before merging:

  1. Add a proper description explaining:

    • What test failures this addresses
    • Why the fast restart was problematic
    • Any investigation/debugging that led to this value
  2. Consider adding a comment in the code explaining why 1 minute is used:

    // time in minutes between each worker restarting when restarting all workers
    // Set to 1 minute to avoid race conditions during CI test runs
    delayBetweenIndividualWorkerRestarts: 1,
  3. Verify CI behavior: Ensure this change actually fixes the failing tests mentioned in the branch name

  4. Monitor CI run times: After merging, watch for any increase in CI execution time

Performance Considerations

  • Low risk - Only affects the dummy app's test configuration
  • Timing impact - Minimal, as worker restarts are periodic (every 2 minutes in CI)

Security & Test Coverage

  • ✅ No security concerns
  • ⚠️ No tests for this configuration change (acceptable for config-only changes)

Overall: The change looks safe but needs better documentation. Please update the PR description with context about what issue this solves before merging.

@claude
Copy link

claude bot commented Nov 12, 2025

Code Review for PR #1992

Thank you for this PR! I have reviewed the changes and have the following feedback:

Summary

This PR makes three main changes:

  1. Increases delayBetweenIndividualWorkerRestarts from 0.01 minutes (0.6 seconds) to 1 minute in CI environments
  2. Adds timestamped logging infrastructure for synchronized log viewing
  3. Creates a new log analysis tool (bin/view-synchronized-logs)

Positives

  1. Excellent debugging infrastructure: The synchronized log viewer is well-designed with color-coded output for Rails vs Node, flexible CLI options, good error handling, and clear documentation

  2. CI improvements: Adding timestamps to logs will greatly improve debugging of timing-related issues

  3. Good documentation: The new script has comprehensive help text and usage examples

Issues and Recommendations

1. Missing PR description

The PR template placeholders have not been filled in. Per CLAUDE.md, you need to explain WHY the delay was increased and what test failures occurred. Understanding the root cause would help determine if 1 minute is the right value or if there is a deeper issue.

2. Code change rationale

The change removes the CI-specific value from 0.01 to 1 minute.

Concerns:

  • Why was the CI-specific value removed? The original code had 0.01 minutes (0.6 seconds) for CI to make tests run faster
  • Performance impact: If multiple workers restart during a test run, this could add significant time to CI runs
  • Is this masking a race condition? If tests were failing with 0.6 second delays, increasing to 60 seconds might hide an underlying synchronization issue

Recommendation: Consider whether this should use an environment variable (RENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTS already exists per configBuilder.ts:167-169), or use a different value like 0.1 or 0.5 minutes as a middle ground

3. Potential issues in CI workflow

The tail -f process runs in the background indefinitely and is never explicitly killed. If log/test.log does not exist after timeout, the script continues silently. Background processes might outlive the CI job or interfere with cleanup.

Recommendation: Add cleanup step or use a timeout mechanism

4. Ruby script quality

The bin/view-synchronized-logs script is well-written. Minor suggestion: Line 48 could handle malformed timestamps more gracefully as Time.parse will raise an exception on invalid input. Good defensive programming on line 95 with rescue for terminal width!

5. Missing checklist items

Per the PR template you need to add/update tests, update documentation, and update CHANGELOG file if this is a user-visible change.

Security and Performance

  • No security concerns identified
  • Performance: 1-minute delay could significantly slow CI runs if workers restart frequently during tests
  • Log artifact storage is properly configured

CHANGELOG Consideration

Per CLAUDE.md, only add CHANGELOG entries for user-visible changes. Based on the file changed (react_on_rails_pro/spec/dummy/client/node-renderer.js), this appears to be test configuration, so likely NO CHANGELOG needed unless it fixes a user-facing bug.

Action Items

  1. Fill in PR description with the root cause and reasoning
  2. Consider if 1 minute is the optimal delay or if a smaller value would suffice
  3. Add cleanup for background tail -f process in CI workflow
  4. Consider adding tests if this fixes a race condition
  5. Ensure RuboCop passes before pushing

Questions for Author

  1. What specific test failures did you observe with the 0.01 minute delay?
  2. Did you test with intermediate values (e.g., 0.1, 0.5 minutes)?
  3. Are there any error messages in CI logs that indicate why the shorter delay failed?
  4. Should this configuration be exposed via environment variable for flexibility?

Overall, the log viewing infrastructure is excellent, but I would like to better understand the rationale for the core configuration change before approving. Great work on the debugging tools!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
.github/workflows/pro-integration-tests.yml (1)

249-258: Consider improving wait logic and log visibility.

Two observations:

  1. The timeout variable is actually a decrementing counter (not seconds), which may be confusing. The actual timeout is 5 seconds (10 * 0.5s).
  2. Unlike the node-renderer logging (line 242), this step doesn't use tee, so the Rails log won't be visible in the CI output—only in the artifact.

Consider this diff for clarity and consistency:

       - name: Tail Rails test log with timestamps in background
         run: |
           cd spec/dummy
           # Wait for log file to be created
-          timeout=10
+          max_iterations=10
+          iteration=0
-          while [ ! -f log/test.log ] && [ $timeout -gt 0 ]; do
+          while [ ! -f log/test.log ] && [ $iteration -lt $max_iterations ]; do
             sleep 0.5
-            timeout=$((timeout - 1))
+            iteration=$((iteration + 1))
           done
-          tail -f log/test.log 2>&1 | ts '[%Y-%m-%d %H:%M:%.S]' > tmp/ci-logs/rails-server.log &
+          tail -f log/test.log 2>&1 | ts '[%Y-%m-%d %H:%M:%.S]' | tee tmp/ci-logs/rails-server.log &
bin/view-synchronized-logs (4)

11-32: Clean class structure with minor style issues.

The overall design is sound—clear separation of concerns with initialization, parsing, and display logic. However, string literals on lines 23-24 should use double quotes for consistency.

Apply this diff:

   def view
-    rails_entries = parse_log_file(@rails_log_path, 'RAILS')
-    node_entries = parse_log_file(@node_log_path, 'NODE')
+    rails_entries = parse_log_file(@rails_log_path, "RAILS")
+    node_entries = parse_log_file(@node_log_path, "NODE")

72-109: Address style issues and moderate complexity.

Several issues to address:

  1. Line 73: Missing empty line after guard clause
  2. Line 77: Cyclomatic complexity is 8/7 (slightly over)
  3. Line 86: Rescue modifier usage can hide errors
  4. Line 98: Single-line if statement should use modifier form

Apply these fixes:

   def matches_filter?(content)
     return true unless @filter
+
     content.match?(@filter)
   end

   def display_synchronized_logs(rails_entries, node_entries)
     all_entries = (rails_entries + node_entries).sort_by { |e| e[:time] }

     if all_entries.empty?
       puts "No entries to display (possibly filtered out)."
       return
     end

     # Get terminal width
-    terminal_width = `tput cols`.to_i rescue 160
+    terminal_width = begin
+      `tput cols`.to_i
+    rescue StandardError
+      160
+    end
     column_width = [(terminal_width - 5) / 2, 60].max

     # Print header
     print_header(column_width)

     # Display entries
     last_time = nil
     all_entries.each do |entry|
       # Show time gap if requested
       if @show_time_gaps && last_time
         gap = entry[:time] - last_time
-        if gap > @time_window
-          print_time_gap(gap, column_width)
-        end
+        print_time_gap(gap, column_width) if gap > @time_window
       end

       print_entry(entry, column_width)
       last_time = entry[:time]
     end

     # Print summary
     print_summary(rails_entries, node_entries, all_entries)
   end

111-167: Multiple style issues in display helper methods.

Several linting issues need to be addressed:

  • Lines 112, 121, 155: Ambiguous operator precedence (wrap in parentheses)
  • Lines 132: Use modifier if for single-line body
  • Line 133, 155: Prefer string interpolation over concatenation
  • Lines 137, 145, 226, 227: Use double-quoted strings
  • Line 161: Use guard clause instead of wrapping in conditional

Apply this diff to address the issues:

   def print_header(column_width)
-    separator = "=" * (column_width * 2 + 5)
+    separator = "=" * ((column_width * 2) + 5)
     puts separator
     rails_header = "RAILS SERVER".center(column_width)
     node_header = "NODE RENDERER".center(column_width)
     puts "#{rails_header} | #{node_header}"
     puts separator
   end

   def print_time_gap(gap, column_width)
-    gap_text = format("--- %.1f second gap ---", gap).center(column_width * 2 + 5)
+    gap_text = format("--- %.1f second gap ---", gap).center((column_width * 2) + 5)
     puts "\e[90m#{gap_text}\e[0m" # Gray color
   end

   def print_entry(entry, column_width)
     timestamp = entry[:time].strftime("%H:%M:%S.%3N")
     content = entry[:content].strip
     source = entry[:source]

     # Truncate content if too long
     max_content_length = column_width - 13 # Account for timestamp
-    if content.length > max_content_length
-      content = content[0...max_content_length - 3] + "..."
-    end
+    content = "#{content[0...max_content_length - 3]}..." if content.length > max_content_length

     # Color codes
-    color = source == 'RAILS' ? "\e[34m" : "\e[32m" # Blue for Rails, Green for Node
+    color = source == "RAILS" ? "\e[34m" : "\e[32m" # Blue for Rails, Green for Node
     reset = "\e[0m"
     time_color = "\e[90m" # Gray for timestamp

     # Format entry with timestamp
     formatted_entry = "#{time_color}#{timestamp}#{reset} #{color}#{content}#{reset}"

     # Print in appropriate column
-    if source == 'RAILS'
+    if source == "RAILS"
       left_content = formatted_entry.ljust(column_width + 20) # +20 for ANSI codes
       puts "#{left_content} |"
     else
       left_padding = " " * column_width
       puts "#{left_padding} | #{formatted_entry}"
     end
   end

   def print_summary(rails_entries, node_entries, all_entries)
-    puts "\n" + "=" * 80
+    puts "\n#{"=" * 80}"
     puts "Summary:"
     puts "  Rails entries: #{rails_entries.size}"
     puts "  Node entries: #{node_entries.size}"
     puts "  Total entries: #{all_entries.size}"

-    if all_entries.any?
-      duration = all_entries.last[:time] - all_entries.first[:time]
-      puts "  Time span: #{format('%.2f', duration)} seconds"
-      puts "  First entry: #{all_entries.first[:time].strftime('%Y-%m-%d %H:%M:%S.%3N')}"
-      puts "  Last entry: #{all_entries.last[:time].strftime('%Y-%m-%d %H:%M:%S.%3N')}"
-    end
+    return unless all_entries.any?
+
+    duration = all_entries.last[:time] - all_entries.first[:time]
+    puts "  Time span: #{format('%.2f', duration)} seconds"
+    puts "  First entry: #{all_entries.first[:time].strftime('%Y-%m-%d %H:%M:%S.%3N')}"
+    puts "  Last entry: #{all_entries.last[:time].strftime('%Y-%m-%d %H:%M:%S.%3N')}"
   end

170-243: Well-designed CLI interface with minor style issues.

The command-line interface is well-structured with clear help text, flexible input options (directory or individual files), and proper validation. The only issues are string literal style violations on lines 226-227.

Fix the string literal style:

-  rails_log = File.join(logs_dir, 'rails-server.log')
-  node_log = File.join(logs_dir, 'node-renderer.log')
+  rails_log = File.join(logs_dir, "rails-server.log")
+  node_log = File.join(logs_dir, "node-renderer.log")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a478eb9 and 8257b20.

📒 Files selected for processing (2)
  • .github/workflows/pro-integration-tests.yml (2 hunks)
  • bin/view-synchronized-logs (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Lint JS and Ruby
bin/view-synchronized-logs

[warning] 1-1: Lint/ScriptPermission: Script file view-synchronized-logs doesn't have execute permission.


[warning] 4-4: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 5-5: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 23-23: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 24-24: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 36-36: Metrics/CyclomaticComplexity: Cyclomatic complexity for parse_log_file is too high. [12/7]


[warning] 36-36: Metrics/PerceivedComplexity: Perceived complexity for parse_log_file is too high. [13/10]


[warning] 52-52: Style/PerlBackrefs: Prefer ::Regexp.last_match(1) over $1.


[warning] 53-53: Style/PerlBackrefs: Prefer ::Regexp.last_match(2) over $2.


[warning] 73-73: Layout/EmptyLineAfterGuardClause: Add empty line after guard clause.


[warning] 77-77: Metrics/CyclomaticComplexity: Cyclomatic complexity for display_synchronized_logs is too high. [8/7]


[warning] 86-86: Style/RescueModifier: Avoid using rescue in its modifier form.


[warning] 98-98: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.


[warning] 112-112: Lint/AmbiguousOperatorPrecedence: Wrap expressions with varying precedence with parentheses to avoid ambiguity.


[warning] 121-121: Lint/AmbiguousOperatorPrecedence: Wrap expressions with varying precedence with parentheses to avoid ambiguity.


[warning] 132-132: IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.


[warning] 133-133: Style/StringConcatenation: Prefer string interpolation to string concatenation.


[warning] 137-137: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 145-145: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 155-155: Style/StringConcatenation: Prefer string interpolation to string concatenation.


[warning] 155-155: Lint/AmbiguousOperatorPrecedence: Wrap expressions with varying precedence with parentheses to avoid ambiguity.


[warning] 161-161: Style/GuardClause: Use a guard clause (return unless all_entries.any?) instead of wrapping the code inside a conditional expression.


[warning] 226-226: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 227-227: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review
🔇 Additional comments (3)
.github/workflows/pro-integration-tests.yml (3)

233-236: LGTM!

Clear installation of the timestamp utility with good documentation explaining its purpose for log synchronization.


238-242: LGTM!

The timestamped logging implementation correctly captures both stdout and stderr, applies timestamps in the expected format, and preserves log output while writing to file. The background execution is appropriate for this long-running process.


319-324: LGTM!

The artifact upload step correctly captures the synchronized logs directory and uses if: always() to ensure logs are available even when tests fail—critical for debugging.

Comment on lines 1 to 10
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'time'
require 'optparse'

# Script to view synchronized logs from CI runs side-by-side
# Usage: bin/view-synchronized-logs path/to/logs/directory
# bin/view-synchronized-logs --rails rails.log --node node.log

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing execute permission on script file.

The script file doesn't have execute permission, which will prevent it from being run directly. Additionally, there are string literal style inconsistencies (lines 4-5).

Apply these fixes:

chmod +x bin/view-synchronized-logs

And update the string literals:

-require 'time'
-require 'optparse'
+require "time"
+require "optparse"
🧰 Tools
🪛 GitHub Actions: Lint JS and Ruby

[warning] 1-1: Lint/ScriptPermission: Script file view-synchronized-logs doesn't have execute permission.


[warning] 4-4: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.


[warning] 5-5: Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

🤖 Prompt for AI Agents
In bin/view-synchronized-logs around lines 1 to 10, the script is missing the
executable bit and has inconsistent string literal styles on lines 4-5; make the
file executable (chmod +x bin/view-synchronized-logs) and normalize the require
strings to the project style (e.g., change require 'time' and require 'optparse'
to use double quotes if the repo prefers double quotes or convert all to single
quotes to match project linting) so the script can be run directly and string
literals are consistent.

Comment on lines 36 to 70
def parse_log_file(path, source)
entries = []
return entries unless File.exist?(path)

current_timestamp = nil
current_lines = []

File.readlines(path).each do |line|
if line =~ TIMESTAMP_REGEX
# Save previous entry if exists
if current_timestamp && !current_lines.empty?
content = current_lines.join
entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
end

# Start new entry
current_timestamp = Time.parse($1)
current_lines = [$2]
elsif current_timestamp
# Continuation of previous entry
current_lines << line
end
end

# Add final entry
if current_timestamp && !current_lines.empty?
content = current_lines.join
entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
end

entries
rescue StandardError => e
warn "Error parsing #{path}: #{e.message}"
[]
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Reduce cyclomatic complexity and avoid Perl-style backrefs.

The parse_log_file method has high cyclomatic (12/7) and perceived (13/10) complexity. Additionally, lines 52-53 use discouraged Perl-style backrefs ($1, $2) instead of Regexp.last_match.

Consider extracting helper methods to reduce complexity:

   def parse_log_file(path, source)
     entries = []
     return entries unless File.exist?(path)

     current_timestamp = nil
     current_lines = []

     File.readlines(path).each do |line|
       if line =~ TIMESTAMP_REGEX
-        # Save previous entry if exists
-        if current_timestamp && !current_lines.empty?
-          content = current_lines.join
-          entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
-        end
+        save_entry(entries, current_timestamp, current_lines, source) if current_timestamp
 
         # Start new entry
-        current_timestamp = Time.parse($1)
-        current_lines = [$2]
+        current_timestamp = Time.parse(Regexp.last_match(1))
+        current_lines = [Regexp.last_match(2)]
       elsif current_timestamp
         # Continuation of previous entry
         current_lines << line
       end
     end

-    # Add final entry
-    if current_timestamp && !current_lines.empty?
-      content = current_lines.join
-      entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
-    end
+    save_entry(entries, current_timestamp, current_lines, source) if current_timestamp
 
     entries
   rescue StandardError => e
     warn "Error parsing #{path}: #{e.message}"
     []
   end
+
+  def save_entry(entries, timestamp, lines, source)
+    return if lines.empty?
+    
+    content = lines.join
+    entries << { time: timestamp, content: content, source: source } if matches_filter?(content)
+  end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def parse_log_file(path, source)
entries = []
return entries unless File.exist?(path)
current_timestamp = nil
current_lines = []
File.readlines(path).each do |line|
if line =~ TIMESTAMP_REGEX
# Save previous entry if exists
if current_timestamp && !current_lines.empty?
content = current_lines.join
entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
end
# Start new entry
current_timestamp = Time.parse($1)
current_lines = [$2]
elsif current_timestamp
# Continuation of previous entry
current_lines << line
end
end
# Add final entry
if current_timestamp && !current_lines.empty?
content = current_lines.join
entries << { time: current_timestamp, content: content, source: source } if matches_filter?(content)
end
entries
rescue StandardError => e
warn "Error parsing #{path}: #{e.message}"
[]
end
def parse_log_file(path, source)
entries = []
return entries unless File.exist?(path)
current_timestamp = nil
current_lines = []
File.readlines(path).each do |line|
if line =~ TIMESTAMP_REGEX
save_entry(entries, current_timestamp, current_lines, source) if current_timestamp
# Start new entry
current_timestamp = Time.parse(Regexp.last_match(1))
current_lines = [Regexp.last_match(2)]
elsif current_timestamp
# Continuation of previous entry
current_lines << line
end
end
save_entry(entries, current_timestamp, current_lines, source) if current_timestamp
entries
rescue StandardError => e
warn "Error parsing #{path}: #{e.message}"
[]
end
def save_entry(entries, timestamp, lines, source)
return if lines.empty?
content = lines.join
entries << { time: timestamp, content: content, source: source } if matches_filter?(content)
end
🧰 Tools
🪛 GitHub Actions: Lint JS and Ruby

[warning] 36-36: Metrics/CyclomaticComplexity: Cyclomatic complexity for parse_log_file is too high. [12/7]


[warning] 36-36: Metrics/PerceivedComplexity: Perceived complexity for parse_log_file is too high. [13/10]


[warning] 52-52: Style/PerlBackrefs: Prefer ::Regexp.last_match(1) over $1.


[warning] 53-53: Style/PerlBackrefs: Prefer ::Regexp.last_match(2) over $2.

🤖 Prompt for AI Agents
In bin/view-synchronized-logs around lines 36 to 70, parse_log_file is overly
complex and uses Perl-style backrefs ($1/$2); refactor by extracting small
helpers (e.g., parse_line_match(line) to return timestamp and body,
flush_current_entry(entries, current_timestamp, current_lines, source) to handle
pushing filtered entries) to reduce cyclomatic complexity and move
parsing/append logic out of the main loop, replace $1/$2 with Regexp.last_match
(or use named captures) to obtain the timestamp and message, and ensure early
returns and single-responsibility helpers so the main method becomes a simple
loop delegating matching, appending, and error handling.

@coderabbitai coderabbitai bot added the bug label Nov 12, 2025
@claude
Copy link

claude bot commented Nov 12, 2025

Code Review for PR #1992

Summary

This PR removes the conditional logic that selected different HTTP connection pools based on whether the request is a streaming request or not. Previously, streaming requests used connection_without_retries while non-streaming requests used connection (with retries enabled).

Change: Line 102 now always uses connection (the pool with retries enabled) for all requests, removing the conditional based on post_options[:stream].


Analysis

Context from Code Comments

The existing comments at lines 87-89 and 100-101 explain the architectural decision:

  • Two separate HTTP connection pools exist (doubling memory footprint)
  • This was done to prevent body duplication in streaming responses
  • The StreamRequest class was designed to handle retries properly for streaming requests

The Change

# Before:
conn = post_options[:stream] ? connection_without_retries : connection

# After:
conn = connection

Critical Issues

🚨 1. Contradicts Documented Architecture (Lines 87-102)

The code comments explicitly state:

"For streaming requests, use connection without retries to prevent body duplication"

Yet this PR now uses the retry-enabled connection for all requests, including streaming ones.

This appears to reintroduce the issue fixed in #1895 (mentioned in line 245).

🚨 2. Potential Body Duplication in Streams

When HTTPX retries a streaming request with the retry plugin enabled, it may send duplicate chunks to the client if the connection drops mid-stream. This is precisely what the two-pool architecture was designed to prevent.

From line 78-80 in stream_request.rb:

# Retry logic for streaming requests is handled here by starting fresh requests.
# The HTTPx connection used for streaming has retries disabled (see Request#connection_without_retries)
# to prevent body duplication when partial chunks are already sent to the client.

This comment in StreamRequest confirms that streaming requests should not use the retry-enabled connection.

🚨 3. Dead Code

If connection_without_retries is no longer used:

  • The @connection_without_retries instance variable (line 12, 14, 96) becomes dead code
  • The comment block at lines 87-89 is now misleading
  • The enable_retries parameter in create_connection (line 234) may also be unused

Questions for Author

  1. What problem does this solve? The PR title mentions "Increase delayBetweenIndividualWorkerRestarts" but the actual change is completely different.

  2. Has this been tested with streaming requests? Specifically:

    • RSC (React Server Components) rendering (render_code_as_stream at line 23)
    • Can you confirm no body duplication occurs when HTTPX retries a stream?
  3. Is this related to issue [Important] Fix the body duplication problem when an retry happen after a connection error #1895? That issue specifically dealt with streaming and retries.

  4. Should the PR title be updated to reflect what actually changed?


Potential Impact

High Risk Areas:

  • React Server Components (RSC): Uses streaming extensively via render_code_as_stream
  • Any partial stream rendering: Could see duplicate content if connection drops and retries

Lower Risk:

  • Non-streaming requests (render_code, upload_assets, etc.) - these were already using connection

Recommendations

Before Merging:

  1. Clarify Intent: Update PR description to explain:

    • Why this change is needed
    • Whether you intend to remove the two-pool architecture
    • How body duplication is now prevented
  2. Update Documentation:

    • Remove or update the misleading comment at lines 87-89
    • Update stream_request.rb line 79 comment about connection_without_retries
  3. Clean Up Dead Code:

    • Remove @connection_without_retries and related code if truly unused
    • Remove enable_retries parameter if no longer needed
  4. Testing Required:

    • Add/run tests for streaming requests with connection failures
    • Verify no body duplication with RSC streaming
    • Test with actual network interruptions
  5. Consider Alternative Solutions:

    • If the two-pool architecture is problematic, document why
    • Consider if StreamRequest's retry logic is sufficient on its own
    • Evaluate if HTTPX retry behavior has changed in newer versions

Code Quality:

  • ✅ No RuboCop violations introduced
  • ⚠️ Architecture change not explained
  • ❌ PR checklist incomplete (no tests, docs, changelog)
  • ❌ Contradicts existing code comments

Security & Performance

  • Security: No direct security issues, but unexpected duplicate content could have security implications depending on what's being streamed
  • Performance: Removes half of the HTTP connection pool, saving memory (positive side effect if safe)

Verdict: ⛔ Changes Requested

This appears to be a significant architectural change that contradicts documented behavior and existing comments. Without clear explanation of intent and thorough testing of streaming scenarios, this risks reintroducing the body duplication bug that was previously fixed.

Please provide context for this change and update the PR description, title, and documentation accordingly.


Review conducted per repository CLAUDE.md guidelines. Questions? Let me know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd802df and bc6b3db.

📒 Files selected for processing (1)
  • react_on_rails_pro/lib/react_on_rails_pro/request.rb (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review

# For streaming requests, use connection without retries to prevent body duplication
# The StreamRequest class handles retries properly by starting fresh requests
conn = post_options[:stream] ? connection_without_retries : connection
conn = connection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Restore non-retrying connection for streaming.

Switching streaming requests to the retry-enabled pool reintroduces the body duplication bug called out just above (Lines 100-101, plus the note referencing issue #1895). We still need connection_without_retries when post_options[:stream] is true; otherwise retries can replay partial stream bodies and break clients. Please keep the conditional selection so streaming uses the non-retry pool.

-        conn = connection
+        conn = post_options[:stream] ? connection_without_retries : connection
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
conn = connection
conn = post_options[:stream] ? connection_without_retries : connection
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/request.rb around line 102, restore
the conditional so streaming requests use the non-retrying connection: set conn
= connection_without_retries when post_options[:stream] is true, otherwise set
conn = connection; ensure the conditional branch covers only the stream case and
leaves non-stream requests using the retry-enabled pool.

@claude
Copy link

claude bot commented Nov 12, 2025

Code Review for PR #1992

Critical Issue: PR Title and Description Mismatch

The PR title says "Increase delayBetweenIndividualWorkerRestarts" but the actual code change is completely unrelated. This PR modifies HTTP connection retry behavior for streaming requests, not worker restart delays.


What This PR Actually Does

File changed: react_on_rails_pro/lib/react_on_rails_pro/request.rb

Change: Line 102 now uses connection (with retries) for ALL requests, removing the conditional that previously used connection_without_retries for streaming requests.

# Before:
conn = post_options[:stream] ? connection_without_retries : connection

# After:
conn = connection

🚨 Major Concerns

1. This appears to reintroduce the body duplication bug from #1895

Issue #1895 specifically documented that when HTTPX retries a streaming request:

  • Some HTML chunks may already be sent to the client
  • Retrying duplicates those chunks in the response
  • This causes DOM errors and hydration failures

The fix for #1895 was to use a separate connection without retries for streaming requests. This PR removes that fix.

2. Contradicts existing code comments

Lines 87-89 explicitly state:

"We maintain two separate HTTP connection pools to handle streaming vs non-streaming requests... This tradeoff is acceptable to prevent body duplication in streaming responses."

Lines 100-101:

"For streaming requests, use connection without retries to prevent body duplication. The StreamRequest class handles retries properly by starting fresh requests."

Your change makes these comments incorrect.

3. StreamRequest design relies on no-retry connection

stream_request.rb line 78-80 documents:

"The HTTPx connection used for streaming has retries disabled (see Request#connection_without_retries) to prevent body duplication when partial chunks are already sent to the client."

The StreamRequest class implements its own retry logic (lines 81-92) specifically because the underlying connection should NOT retry automatically.

4. Dead code implications

If this change is intentional, the following code becomes dead:

  • @connection_without_retries instance variable (lines 12, 14, 96)
  • The enable_retries parameter in create_connection (line 234)
  • The entire comment block at lines 87-89

Questions for Author

  1. Why was this change made? The PR description is completely empty except for the template.

  2. Has this been tested with streaming requests? Specifically:

    • React Server Components (RSC) rendering
    • Pages like /rsc_posts_page_over_redis that stream content
    • What happens when a connection error occurs mid-stream?
  3. Is the PR title incorrect? Should it be something like "Simplify HTTP connection handling" or "Remove dual connection pool architecture"?

  4. Have you observed body duplication? With this change, retrying a streaming request should cause duplicate content to be sent.

  5. What problem does this solve? Without understanding the motivation, it's impossible to evaluate if this is the right approach.


Recommendations

Before Merging:

🛑 Do not merge this PR without:

  1. Complete the PR description explaining:

    • What problem you're solving
    • Why the dual-connection architecture is no longer needed
    • How body duplication is now prevented
    • Test results showing no regression
  2. Update documentation to match the new behavior:

    • Remove/update comment at lines 87-89 in request.rb
    • Update comment at line 78-80 in stream_request.rb
    • Update any related docs about streaming and retries
  3. Clean up dead code:

    • Remove @connection_without_retries and connection_without_retries method
    • Remove enable_retries parameter from create_connection
    • Update reset_connection method (line 10-15)
  4. Thorough testing required:

  5. Complete PR checklist:

    • Add/update tests
    • Update documentation
    • Update CHANGELOG (if this is user-visible)
    • Fix PR title to match actual changes

Potential Alternative

If the goal is to simplify the architecture, consider whether:

  • HTTPX retry behavior has changed in newer versions
  • StreamRequest's retry logic alone is sufficient
  • There's a better way to prevent body duplication

Impact Assessment

High Risk Areas:

  • ❌ React Server Components (uses streaming extensively)
  • ❌ Any partial stream rendering
  • ❌ Connection recovery during streaming
  • ❌ Could see duplicate content when connections drop and retry

Test Coverage:

  • ❌ No tests added for this behavior change
  • ❌ PR checklist not completed

Code Quality:

  • ✅ No RuboCop issues introduced
  • ❌ Contradicts existing comments and documentation
  • ❌ Breaks documented architecture decisions

Verdict: ⛔ Changes Requested

This appears to be a significant architectural change that:

  1. Contradicts documented behavior and existing comments
  2. May reintroduce a previously-fixed body duplication bug
  3. Has no explanation, tests, or documentation updates
  4. Has an incorrect PR title

Please do not merge until the above concerns are addressed.


Review conducted per repository CLAUDE.md guidelines. Based on codebase analysis and issue #1895 investigation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants